Object Oriented Intuition - Part 2

by John Corigliano

<jcorig@udel.edu>

Last month I illustrated the class heirarchy and discussed each class. This month I will introduce the IApplication class and also discuss the much needed classlib.

The Top Level Class

Every program that uses OOI will have an object of type IApplication. This class is mostly just the wrapper around the event loop that just about every Amiga program has. You know what I'm talking about:
while (!done) {
    sig = Wait(sig1 | sig2 | sig3)
    if (sig & sig1) ...
    else if (sig & sig2) ...
    else if (sig & sig3) ...
    else ....
}

Well, with OOI you will never have to code this again! All you'll have to do is register an event with IApplication and it will automatically handle waiting for signals.

Registering Events

An event, in the OOI sense, is one of the following things: The astute reader may notice that each of these things are all really just signals, but OOI will handle each one differently.

To register an event, you just need to instantiate an object of one of these three (3) classes (or an object derived from one):

Then, call the IApplication class member function AddEvent() with a reference to the object. For example:
// Derive a class from IMessagePort
class MyPort : public IMessagePort
{
    public:
        ...
        Message * EventHandler(Message *m);
};

// Derive a class from IUserWindow
class MyWindow : public IUserWindow
{
    public:
        DECLARE_RESPONSE_TABLE;

    protected:
        void HandleButton1();
        void HandleButton2();
        void HandleRawkey(int code, int qualifier);
    ...
};

// Derive a class from IApplication
class MyApp : public IApplication
{
    public:
       SetupWindow();
    protected:
       MyPort *port;
};

// The main function
main()
{
    MyApp app();
    app.Run;
}

// Create the window and port and add the events
void MyApp::SetupWindow()
{
    MainWindow = new MyWindow();
    AddEvent(*MainWindow);

    port = new MyPort;
    AddEvent(*port);

    ...
}

// Create the response table for class MyWindow
DEFINE_RESPONSE_TABLE1(MyWindow, IUserWindow)
  ID_GADGETUP(BUTTON_1, HandleButton1)
  ID_GADGETUP(BUTTON_2, HandleButton2)
  ID_RAWKEY(HandleRawkey)
END_RESPONSE_TABLE

// Create the functions for handling events
void MyWindow::HandleButton1()
{
    ...
}

void MyWindow::HandleButton2()
{
    ...
}

void MyWindow::HandleRawkey(int code, int qualifier)
{
    ....
}

First, we define a class called MyPort, which is derived from the pre-defined class IMessagePort. This class's main job is to override IMessagePort's EventHandler() function. IApplication calls this function when a message arrives at the port.

The MyWindow class is a bit different. If any of you have ever used Borland's OWL for the PC this should look very familiar. Instead of having to define a single EventHandler function, classes derived from IUserWindow need to supply a member function for each event it wishes to handle. In the example, the MyWindow class wants to handle IDCMP_GADGETDOWN messages from BUTTON_1 and BUTTON_2, as well as IDCMP_RAWKEY events.

The tricks to setting all this up are the macros DEFINE_ and DECLARE_RESPONSE_TABLE (the concept here was borrowed form OWL, but my implementation is much different :). Together, these two macros will declare and define two member functions. I haven't actually written these macros yet, but they will look something like this:

#define DECLARE_RESPONSE_TABLE(cls) \
    IntuiMessage * EventHandler(IntuiMessage *i);\
    IntuiMessage * EventStub(IntuiMessage *i)

#define DEFINE_RESPONSE_TABLE1(cls,base) \
    IntuiMessage * cls::EventHandler(IntuiMessage *i) { \
        if (NULL != EventStub(i)) return base::EventHandler(i); \
        else return NULL; \
    } \
    IntuiMessage *cls::EventStub(IntuiMessage *i) { \
        if (NULL == i) return NULL;

#define ID_GADGETUP(b,f) \
        else if (i->Class == IDCMP_GADGETUP && \
                    ((Gadget *)(i->IAddress))->GadgetID == b) {\
            f(); \
            ::ReplyMessage((Message *)i); \
            return NULL; \
        }

#define ID_RAWKEY(f) \
        else if (i->Class == IDCMP_RAWKEY) {\
            f(i->Code, i->Qualifier); \
            ::ReplyMessage((Message *)i); \
            return NULL; \
        }

#define END_RESPONSE_TABLE \
        else return i;\
    }

So, when all is said and done, the macros above would pre-process into:
IntuiMessage * MyWindow::EventHandler(IntuiMessage *i) {
    if (NULL != EventStub(i)) return IUserWindow::EventHandler(i);
    else return NULL;
}
IntuiMessage *MyWindow::EventStub(IntuiMessage *i) {
    if (NULL == i) return NULL;
    else if (i->Class == IDCMP_GADGETUP &&
                   ((Gadget *)(i->IAddress))->GadgetID == BUTTON_1) {
        HandleButton1();
        ::ReplyMessage((Message *)i);
        return NULL;
    }
    else if (i->Class == IDCMP_GADGETUP &&
                   ((Gadget *)(i->IAddress))->GadgetID == BUTTON_2) {
        HandleButton2();
        ::ReplyMessage((Message *)i);
        return NULL;
    }
    else if (i->Class == IDCMP_RAWKEY) {\
        HandleRawkey(i->Code, i->Qualifier); \
        ::ReplyMessage((Message *)i); \
        return NULL;
    }
    else return i;
}

Pretty groovy, huh?

Whenever you add an event, IApplication adds it to one of three internal linked lists (depending on what type of event you are adding). Also, it ORs the bitmask into a global bitmask that is can use for the exec.library Wait() function:

void IApplication::AddEvent(IMessagePort& port)
{
    PortList.Add(&port);
    WaitMask |= port.GetSignalMask();
}

Of course, there will also be two other AddEvent() functions: one for IUserWindow and one for ISignal.

When your main() function executes app.Run(), the IApplication class eventually enters it's EventLoop() function:

void IApplication::EventLoop()
{
    BOOL done = FALSE;
    IntuiMessage *i;
    Message *m;
    ULONG sig;

    while (!done) {
        sig = Wait(WaitMask);

        // Walk through window list
        WindowList.Rewind();
        while (IUserWindow *win = WindowList++) {
            if (win->GetSignalMask() & sig) {
                i = (IntuiMessage *)GetMessage(win->GetUserPort());
                if (NULL != win->EventHandler(i))
                    ReplyMessage((Message *)i);
                sig = 0;                // Mark as handled
                break;
            }
        }
        // Walk through port list
        if (0 != sig) {
            PortList.Rewind();
            while (IMessagePort *p = PortList++) {
                if (p->GetSignalMask() & sig) {
                    m = GetMessage(p->GetPort());
                    if (NULL != p->EventHandler(m))
                        ReplyMessage(m);
                    sig = 0;                // Mark as handled
                    break;
                }
            }
        }
        // Walk through signal list
        if (0 != sig) {
            SignalList.Rewind();
            while (ISignal *s = SignalList++) {
                if (s->GetSignalMask() & sig) {
                    s->EventHandler();
                }
            }
        }
    }
}


The Classlib

As important as Intuition is to Amiga programming, there's much more to the Amiga OS. Thus, it is neccessary to create a companion library that takes care of everything else that OOI doesn't cover. For example, I've already started working on this, but...

Help Wanted!

This project is going to be huge and there is just no way that I can do it alone. I need help (in more ways than one :). Somebody is already helping me with IGadgets (thanks Laurie :), but there are plenty of other classes to be made. If you interested, send me some e-mail.


No Code

Due to time restraints, there is no code to download this month, but there should be some next month.


Table Of Contents